/* Random/sequential read/write benchmark. */
/* Copyright 2005-2006 Gopalan Sivathanu */
/* Copyright 2005-2006 Charles P. Wright */
/* Copyright 2005-2006 The Research Foundation of SUNY  */

/* We need large file support so we have 2x memory, but we need to test the
 * monitor's large file support first.
 */
#define _GNU_SOURCE
#define __USE_LARGEFILE64
#define _FILE_OFFSET_BITS 64

#ifndef O_DIRECT
#define O_DIRECT 040000
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <linux/limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <time.h>

#define APP "micro"

#define READ 1
#define WRITE 2
#define SEQ 3
#define RAND 4

#define PROFILE_SYSCALLS

#ifdef PROFILE_SYSCALLS

#define OP_FORK		0
#define OP_LLSEEK	1
#define OP_PREAD	2
#define OP_PWRITE	3
#define OP_OPEN		4
#define OP_CLOSE	5
#define OP_MAX		6

#define BUCKETS		32

char* op_names[] = {"OP_FORK", "OP_LSEEK", "OP_PREAD", "OP_PWRITE", "OP_OPEN", "OP_CLOSE"};

int* aggregate_stats;
unsigned long long* aggregate_count;
int shmid;

#define RDTSC(qp) \
	do { \
		unsigned long lowPart, highPart; \
		__asm__ __volatile__("rdtsc" : "=a" (lowPart), "=d" (highPart)); \
		qp = (((unsigned long long) highPart) << 32) | lowPart; \
	} while (0);

#define AGGREGATE_PRE \
	long long lAggregate; \
	RDTSC(lAggregate);

#define AGGREGATE_POST(op) \
	long long ll; \
	int bucket, delay; \
	RDTSC(ll); \
	delay = (int)(ll - lAggregate); \
	aggregate_count[(op * 2)]++; \
	aggregate_count[(op * 2) + 1] += delay; \
	delay = (int)(delay >> 5); \
	bucket = 0; \
	while (delay >>= 1) \
		bucket++; \
	aggregate_stats[op * BUCKETS + bucket]++;

#define AGGREGATE_STATS(what, op) \
	do { \
		AGGREGATE_PRE; \
		what; \
		AGGREGATE_POST((op)); \
	} while (0);

#define AGGREGATE_STATS_PRINT \
	{ \
		int i, ii; \
		for (i = 0; i < OP_MAX; i++) { \
			printf("%s %llu %llu ", op_names[i], aggregate_count[(i * 2)], aggregate_count[(i * 2) + 1]); \
			for (ii = 0; ii < BUCKETS; ii++) \
				printf("%d ", aggregate_stats[(i * BUCKETS) + ii]); \
			printf ("\n"); \
		} \
	}

#define AGGREGATE_STATS_EXIT	shmdt(aggregate_stats);

#define AGGREGATE_STATS_INIT \
	aggregate_stats = mmap(NULL, BUCKETS * sizeof(int) * OP_MAX, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, 0, 0); \
	aggregate_count = mmap(NULL, 2 * sizeof(unsigned long long) * OP_MAX, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, 0, 0); \
	if (!aggregate_stats || !aggregate_count) { \
		perror("mmap"); \
	} \
	memset(aggregate_stats, 0, BUCKETS * sizeof(int) * OP_MAX); \
	memset(aggregate_stats, 0, 2 * sizeof(unsigned long long) * OP_MAX);

#else
#define AGGREGATE_STATS_INIT
#define AGGREGATE_STATS_PRINT
#define AGGREGATE_STATS_EXIT
#define AGGREGATE_STATS(what, op) what;
#endif // PROFILE_SYSCALLS

int op, pattern, numops, numpro,ssize, debug, timelimit, warmlimit, openflags;
unsigned int seed;
char fname[256];
unsigned int *order;
int elements;

int *didops;
int *warmops;

void usage(void) {
        printf("Usage: micro [--read|--write] [--random|--seq] [-s <stage size>] [-n <num-operations>] [-p <num-of-processes>] [-t <time limit>] -o <filename>\n");
        printf("Default: Read, Random, 10000 ops, 4096 stage size, and 2 threads\n");
}

void parse_args(int num, char **args)	{
	int i=1;

	strcpy(fname, "");

	while (i<num)	{
		if (strcmp(args[i], "--read") == 0)
			op = READ;
		else if (strcmp(args[i], "--write") == 0)
			op = WRITE;
		else if (strcmp(args[i], "--seq") == 0)
			pattern = SEQ;
		else if (strcmp(args[i], "--random") == 0)
			pattern = RAND;
		else if (strcmp(args[i], "--debug") == 0)
			debug = 1;
		else if (strcmp(args[i], "-n") == 0) {
			numops = atoi(args[++i]);
			timelimit = 0;
		}
		else if (strcmp(args[i], "-t") == 0) {
			numops = 0;
			timelimit = atoi(args[++i]);
		}
		else if (strcmp(args[i], "-w") == 0)
			warmlimit = atoi(args[++i]);
		else if (strcmp(args[i], "-s") == 0)
                        ssize = atoi(args[++i]);
		else if (strcmp(args[i], "-p") == 0)
			numpro = atoi(args[++i]);
		else if (strcmp(args[i], "-o") == 0)
			strcpy(fname, args[++i]);
		else if (strcmp(args[i], "-d") == 0)
			openflags |= O_DIRECT;
		else if (strcmp(args[i], "--help") == 0) {
			usage();
			exit(0);
		}
		else {
			printf("%s: Invalid argument '%s' after '%s'\n", APP, args[i], args[i-1]);
			usage();
			exit(1);
		}
		i++;
	}

	if (strcmp(fname, "") == 0) {
		printf("%s: must specify a filename.\n", APP);
		usage();
		exit(1);
	}
}


void assign_defaults(void) {
	op = READ;
	pattern = RAND;
	/* How long to warm up for. */
	warmlimit = 15;
	/* How many seconds to run for. */
	timelimit = 60;
	/* We don't have a fixed number of operations any more. */
	numops = 0;
	numpro = 1;
	ssize = 4096;
	debug = 0;
	strcpy(fname, "");
	seed = time(NULL) * time(NULL) * time(NULL);
	openflags = 0;
}

void print_config(void)	{
	printf("\nCurrent configuration:\n\n");

	printf("Operation: ");
	if (op == READ)
		printf("Read\n");
	else
		printf("Write\n");

	printf("Pattern: ");
	if (pattern == RAND)
		printf("Random\n");
	else
		printf("Sequential\n");

	if (numops) {
		printf("Number of operations: %d\n", numops);
	} else {
		printf("Time limit (seconds): %d\n", timelimit);
		printf("Warmup (seconds): %d\n", warmlimit);
	}
	printf("Number of processes: %d\n", numpro);
	printf("Staging size: %d\n", ssize);
	printf("File to operate: %s\n", fname);
	printf("Seed: %u\n", seed);
	fflush(stdout);

}

int perform_ops() {
        int i, pno, ret, fd, start, end, t;
	int ops = 0;
	void *buf;
	struct timeval now, stop;
	int in_warmup = 0;

	if (timelimit) {
		gettimeofday(&stop, NULL);
		if (warmlimit) {
			stop.tv_sec += warmlimit;
			in_warmup = 1;
		} else {
			stop.tv_sec += timelimit;
		}
	}

        //fork off processes

	if (op == READ) {
		AGGREGATE_STATS(fd = open(fname, openflags | O_RDONLY), OP_OPEN);
	} else {
		AGGREGATE_STATS(fd = open(fname, openflags | O_WRONLY), OP_OPEN);
	}

	if (fd == -1) {
		perror("open");
		exit(1);
	}

        for (pno=0; pno<numpro; pno++)	{
                AGGREGATE_STATS(ret = fork(), OP_FORK);

	        if (ret == 0)
			goto child;

	        if (ret == -1)  {
			perror("fork");
			exit(1);
	        }
	}

        goto parent;

child:
	if (posix_memalign(&buf, 4096, ssize) < 0) {
		fprintf(stderr, "Error: Not enough aligned memory.\n");
		exit(1);
	}

	if (numops) {
		start = pno*numops;
		end = start + numops;
	} else {
		start = (elements / numpro) * pno;
		end = ((elements / numpro) * (pno + 1)) - 1;
	}

	for (i=start; i<end; i++) {
		loff_t pos = order[i] * ssize;

		if (op == READ) {
			AGGREGATE_STATS(ret = pread64(fd, buf, ssize, pos), OP_PREAD);
		} else {
			AGGREGATE_STATS(ret = pwrite64(fd, buf, ssize, pos), OP_PWRITE);
		}

		if (ret < 0) {
			fprintf(stderr, "Micro process %d: pread/pwrite: %s", pno, strerror(errno));
			exit(1);
		}

		ops++;

		if (timelimit) {
			if (i == end - 1) {
				i = start - 1;
			}
			gettimeofday(&now, NULL);
			if ((now.tv_sec == stop.tv_sec && now.tv_usec > stop.tv_usec) || (now.tv_sec > stop.tv_sec)) {
				if (in_warmup) {
					warmops[pno] = ops;
					stop.tv_sec = now.tv_sec + timelimit;
					stop.tv_usec = now.tv_usec;
					in_warmup = 0;
					ops = 0;
					AGGREGATE_STATS_EXIT;
					AGGREGATE_STATS_INIT;
				} else {
					didops[pno] = ops;
					break;
				}
			}
		}
	}

	AGGREGATE_STATS(close(fd), OP_CLOSE);
	return 1;

parent:
	for (i=0; i<numpro; i++) {
		waitpid(-1, &t, 0);
		if (WIFEXITED(t)) {
			if (WEXITSTATUS(t)) {
				printf("\nChild processes exitted with a non-zero status");
				exit(1);
			}
		}
		else {
			printf("\nChild process terminated unexpectedly.\n");
			exit(1);
		}
	}

        printf("Done.\n");
        fflush(stdout);

	return 2;
}

void shuffle(unsigned int *seq, int size) {
	int i,tmp,pos;
	srandom(seed);
	for (i=size-1; i>=0; i--) {
		tmp = seq[i];
		pos = random() % (i+1);
		seq[i] = seq[pos];
		seq[pos] = tmp;
	}
}

int generate_order()    {
	struct stat f_stat;
	int i=0, ret;
	off_t fsize;
	int pages;

	ret = stat(fname, &f_stat);

	if (ret < 0) {
		perror(APP);
		exit(1);
	}

	fsize = f_stat.st_size;
	pages = fsize/ssize;

	elements = pages;
	while (numops && (elements < (numops*numpro)))
		elements += pages;

	order = (unsigned int*)malloc(elements * sizeof(unsigned int));
	if (!order) {
		printf("\nCould not allocate: memory.");
		exit(1);
	}

	for (i = 0; i < elements; i++)
		order[i] = (i % pages);

	if (pattern == RAND)
		shuffle(order, elements);

	return 1;
}

void print_order() {
	int i;

	if (debug) {
		for (i=0; i<numops*numpro; i++)
			printf("%d ", order[i]);
	}
}


int main(int argc, char **argv)	{
	int i;
	struct timeval start;
	struct timeval end;
	struct timeval synctime;

	AGGREGATE_STATS_INIT;

	assign_defaults();
	parse_args(argc, argv);
	print_config();

	printf("Generating order of operations ...\n");
	fflush(stdout);
	generate_order();

	print_order();

	printf("Syncronizing...");
	fflush(stdout);
	sync();
	printf("Done\n");

	printf("Performing operations ...");
	fflush(stdout);

	if (timelimit) {
		didops = mmap(NULL, numpro * 2 * sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, 0, 0);
		if (!didops) {
			perror("mmap");
		}
		warmops = didops + numpro;
		memset(didops, 0, sizeof(int) * numpro);
		memset(warmops, 0, sizeof(int) * numpro);
	}

	gettimeofday(&start, NULL);
	i = perform_ops();
	gettimeofday(&end, NULL);
	if (i == 2) {
		printf("Syncronizing...");
		fflush(stdout);
		sync();
		gettimeofday(&synctime, NULL);
		printf("Done\n");

		AGGREGATE_STATS_PRINT;
		AGGREGATE_STATS_EXIT;
	}

	if (i == 2 && timelimit) {
		unsigned long optotal = 0;
		unsigned long warmtotal = 0;
		unsigned long usecs;
		double oppersec, woppersec;
		struct timeval temp;

		temp.tv_sec = end.tv_sec;
		temp.tv_usec = end.tv_usec;

		if (temp.tv_usec < start.tv_usec) {
			temp.tv_usec += 1000000;
			temp.tv_sec--;
		}
		temp.tv_usec -= start.tv_usec;
		temp.tv_sec -= start.tv_sec;

		usecs = (temp.tv_sec * 1000 * 1000) + temp.tv_usec;

		printf("           %-10s %-10s %-10s %-10s\n", "Warmup", "WOps/Sec", "Ops", "Ops/Sec\n");

		for (i = 0; i < numpro; i++) {
			oppersec = ((double)didops[i])/((double)(usecs - (warmlimit * 1000000)) / 1000000.0);
			if (warmlimit) {
				woppersec = ((double)warmops[i])/(double)warmlimit;
			} else {
				woppersec = 0;
			}
			printf("Thread %-3d %-10d %-10.2f %-10d %-10.2f\n", i, warmops[i], woppersec, didops[i], oppersec);
			optotal += didops[i];
			warmtotal += warmops[i];
		}
		oppersec = ((double)optotal)/((double)(usecs - (warmlimit * 1000000)) / 1000000.0);
		if (warmlimit) {
			woppersec = ((double)warmtotal)/(double)warmlimit;
		} else {
			woppersec = 0;
		}
		printf("Total      %-10lu %-10.2f %-10lu %-10.2f\n", warmtotal, woppersec, optotal, oppersec);

		fflush(stdout);

		fprintf(stderr, "[result]\n");
		fprintf(stderr, "pattern = %s\n", pattern == SEQ ? "seq" : "random");
		fprintf(stderr, "mode = %s\n", op == READ ? "read" : "write");
		fprintf(stderr, "warmlimit = %d\n", warmlimit);
		fprintf(stderr, "timelimit = %d\n", timelimit);

		fprintf(stderr, "ops = %lu\n", optotal);
		fprintf(stderr, "usec = %lu\n", usecs);

		fprintf(stderr, "warmops = %lu\n", warmtotal);

		temp.tv_sec = synctime.tv_sec;
		temp.tv_usec = synctime.tv_usec;

		if (temp.tv_usec < end.tv_usec) {
			temp.tv_usec += 1000000;
			temp.tv_sec--;
		}
		temp.tv_usec -= end.tv_usec;
		temp.tv_sec -= end.tv_sec;

		usecs = (temp.tv_sec * 1000 * 1000) + temp.tv_usec;

		fprintf(stderr, "syncusec = %lu\n", usecs);
		fprintf(stderr, "\n");
	}

	return 0;
}
